/**
 * klasse die die kamera des 015 robosuitcase verwaltet
 * 
 * zugriff auf kamerafunktionen luft einzig ber diese klasse.
 * 
 * als thread implementieren!
 * 
 */
package ch.unizh.ifi.ailab.camera;

import java.awt.Image;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.Vector;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import javax.media.MediaLocator;

import org.apache.log4j.Logger;

import ch.unizh.ifi.ailab.config.ProgramSettings;
import ch.unizh.ifi.ailab.utils.Utils;

/**
 * @author hans jaeckle jaeckle@gmail.com
 * 
 */

public class Camera extends Thread {

	private boolean running = false;

	private boolean record = false;

	private Image actualImage = null;

	private long imageCounter = 0;

	private long videoCounter = 0;

	private long timeout;

	private int fps;

	private int movieWidth = 0;

	private int movieHeight = 0;

	private long lastUpdate = 0;

	private String fileSeparator = System.getProperty("file.separator");

	private Image stdImage = null;

	private Vector movieFileNames = null;

	private String imagePath = null;

	// das imagepanel zur bildwiedergabe
	private ImagePanel myImagePanel = null;

	// properties
	private ProgramSettings myProp = null;

	// logging
	private Logger myLog = Logger.getLogger(Camera.class);

	/**
	 * konstruktor methode - privat da singleton. es kann nur einen geben!
	 */
	public Camera() {
		myLog.info("kamera: initialisiere kamera.");
		myProp = ProgramSettings.getProgramSettings();
		fps = myProp.getIntProperty("camFPS");
		movieWidth = myProp.getIntProperty("camWidth");
		movieHeight = myProp.getIntProperty("camHeight");
		timeout = (long) 900 / fps;
		imagePath = Utils.path(System.getProperty("user.dir"), "images");

		myLog.debug("kamera: setze timeout: " + timeout);
		myImagePanel = new ImagePanel();

		try {
			stdImage = ImageIO.read(new File(Utils.path(System
					.getProperty("user.dir"), "conf" + fileSeparator
					+ "015CamExample.jpg")));
		} catch (IOException e) {
			error(e);
		}
	}

	/**
	 * error methode zum loggen von fehlern.
	 * 
	 * @param e
	 *            exception die zum fehler fhrt.
	 */
	private void error(Exception e) {
		myLog.error("kamera: " + e.toString());
	}

	/**
	 * getActualImage methode zum abholen des aktuellen bildes
	 */
	public Image getActualImage() {
		return actualImage;
	}

	/**
	 * getImage methode zum aktualisieren des bildes. nur zum internen gebrauch.
	 * zugriff von aussen ueber getactualimage!
	 */
	private void getImage() {
		actualImage = stdImage;
		try {
			// bild holen
			Image image = ImageIO.read(new URL(myProp.getProperty("urlCam")));
			actualImage = image;
			// bild anzeigen
			// TODO: das bild sollte vom Imagepanel geholt mit getactualimage
			// geholt werden!
			myImagePanel.updateImage(actualImage);
			imageCounter++;
		} catch (MalformedURLException e) {
			error(e);
		} catch (IOException e) {
			error(e);
		}
	}

	/**
	 * saveCurrentImageAs methode zum speichern des aktuellen bildes. fileName kann einen
	 * beliebigen pfad enthalten oder null, dann wird der standard pfad
	 * genommen.
	 * 
	 * @param fileName
	 *            zum uebergeben des filenamens und des pfades.
	 */
	private void saveCurrentImageAs(String fileName) {
		if (fileName == null) {
			fileName = Utils.path(imagePath, videoCounter + ".jpg");
		}

		FileOutputStream out = null;
		ImageInputStream imIn = null;

		try {
			imIn = ImageIO.createImageInputStream(actualImage);
			out = new FileOutputStream(fileName);

			int data;
			// EOF = -1
			while ((data = imIn.read()) != -1) {
				out.write(data);
			}
			myLog.debug("kamera: bild gespeichert unter:  " + fileName);
		} catch (MalformedURLException e) {
			error(e);
		} catch (IOException e) {
			error(e);
		} finally {
			try {
				if (imIn != null)
					imIn.close();
				if (out != null)
					out.close();
			} catch (IOException e) {
				error(e);
			}
		}
	}

	/**
	 * gibt das zur kamera gehrende ImagePanel zurck.
	 * 
	 * @return current ImagePanel
	 */
	public ImagePanel getImagePanel() {
		return myImagePanel;
	}

	/**
	 * takeSnapShot methode um speichern des aktuellen bildes fr zugriff von
	 * aussen. mit fileName kann ein beliebiger pfad mitgegeben werden. wird
	 * null bergeben wird der standard pfad genommen.
	 * 
	 * @param fileName
	 */
	public void takeSnapShot(String fileName) {
		myLog.info("kamera: mache schnappschuss.");
		if (fileName == null) {
			fileName = Utils.path(imagePath, myProp
					.getProperty("camSnapShotFileName")
					+ imageCounter + ".jpg");
		}
		saveCurrentImageAs(fileName);
	}

	/**
	 * starten und stoppen des aufnahmemodus
	 */
	public void startStopRecording() {
		if (record) {
			record = false;
			myLog.info("kamera: stoppe aufnahme. es wurden " + videoCounter
					+ " bilder aufgezeichnet.");
			Date date = new Date();

			String movieFileName = Utils.path(imagePath, myProp
					.getProperty("camMovieFileName")
					+ date.toString() + ".jpg");

			// Generate the output media locators.
			MediaLocator oml;

			if ((oml = Utils.createMediaLocator(movieFileName)) == null) {
				System.err.println("Cannot build media locator from: "
						+ movieFileName);
				System.exit(0);
			}

			JpegImagesToMovie j2m = new JpegImagesToMovie();
			if (j2m.doIt(movieWidth, movieHeight, fps, movieFileNames, oml)) {
				myLog.debug("camera: movie saved under " + movieFileName);
			} else {
				myLog.error("camera: somehow failed to record movie...");
			}
			j2m = null;
		} else {
			videoCounter = 0;
			record = true;
			movieFileNames = new Vector();
			myLog.info("kamera: starte aufnahme.");
		}
	}

	/**
	 * run methode des thread - mit dem loop
	 */
	public void run() {
		while (running) {
			long now = new Date().getTime();
			if (now - lastUpdate >= timeout) {
				getImage();
				if (record) {
					videoCounter++;
					String movieTempFileName = Utils.path(imagePath, myProp
							.getProperty("camTmpFileNames")
							+ now + ".jpg");
					saveCurrentImageAs(movieTempFileName);
					movieFileNames.addElement(movieTempFileName);
				}
				lastUpdate = now;
			} else {
				try {
					sleep(myProp.getIntProperty("sleepTime") * 2);
				} catch (InterruptedException e) {
					error(e);
				}
			}
		}
	}

	/**
	 * startmethode des thread
	 */
	public void start() {
		if (!running) {
			running = true;
			myLog.info("kamera: starte verbindung mit kamera...");
			super.start();
		}
	}

	/**
	 * stirb methode zum stoppen des thread
	 */
	public void stirb() {
		myLog.info("kamera: stoppe kamera. es wurden " + imageCounter
				+ " bilder heruntergeladen.");
		running = false;
	}

	/**
	 * restart methode - siehe start und stirb methode
	 */
	public void restart() {
		stirb();
		start();
	}
}
